package sf.database.template.freemarker;

import freemarker.cache.StringTemplateLoader;
import freemarker.core.Environment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import sf.common.IgnoreEmptyList;
import sf.database.jdbc.sql.SQLContext;
import sf.database.jdbc.sql.SQLParameter;
import sf.database.template.TemplateConstants;
import sf.database.template.TemplateRender;
import sf.database.template.freemarker.ext.InModel;
import sf.database.template.freemarker.ext.PageDirective;
import sf.database.template.freemarker.ext.PageIgnoreDirective;
import sf.database.template.freemarker.ext.ParaModel;
import sf.database.template.freemarker.ext.TupleModel;
import sf.database.template.freemarker.ext.WhereDirective;
import sf.database.template.sql.SqlHelp;
import sf.tools.codec.HashUtils;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FreemarkerHelp {


    private static FreemarkerHelp instance = null;

    public static FreemarkerHelp getInstance() {
        if (instance == null) {
            instance = new FreemarkerHelp();
        }
        return instance;
    }

    Configuration cfg = new Configuration(Configuration.getVersion());
    StringTemplateLoader stringLoader = new StringTemplateLoader();


    public FreemarkerHelp() {
        cfg.setTemplateLoader(stringLoader);
        cfg.setDefaultEncoding("UTF-8");
        cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.getVersion()));
        Map<String, Object> share = new HashMap<>();
        ParaModel m = new ParaModel();
        share.put("p", m);
        share.put("para", m);
        share.put("sqlIn", new InModel());
        share.put("tuple", new TupleModel());
        share.put("page", new PageDirective());
        share.put("pageIgnore", new PageIgnoreDirective());
        share.put("where", new WhereDirective());
        try {
            cfg.setSharedVariables(share);
        } catch (TemplateModelException e) {
            throw new RuntimeException(e);
        }
    }

    public Configuration getCfg() {
        return cfg;
    }

    public void putTemplate(String key, String templateContent) {
        templateContent = SqlHelp.replaceLineSql(templateContent);
        stringLoader.putTemplate(key.trim(), templateContent.trim());
    }

    public void putAllTemplate(Map<String, String> map) {
        for (Map.Entry<String, String> entry : map.entrySet()) {
            putTemplate(entry.getKey(), entry.getValue());
        }
    }

    public Template getTemplate(String key) {
        try {
            return cfg.getTemplate(key);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public Template getTemplate(String key, String dbType) {
        try {
            Template template = cfg.getTemplate(TemplateRender.wrapKey(key, dbType), null, null, null, true, true);
            if (template == null) {
                template = cfg.getTemplate(key);
            }
            return template;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public String render(String key, String dbType, Map<String, Object> params, List<Object> paraList) {
        String sql = null;
        try {
            Template template = getTemplate(key, dbType);
            StringWriter out = new StringWriter();
            params.put(TemplateConstants.SQL_PARA_KEY, paraList);
            template.process(params, out);
            sql = out.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return sql;
    }

//    public String renderContent(String content, Map<String, Object> params, List<Object> paraList) {
//        String sql = null;
//        try {
//            Template template = new Template("", content, cfg);
//            StringWriter out = new StringWriter();
//            params.put(SQL_PARA_KEY, paraList);
//            template.process(params, out);
//            sql = out.toString();
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }
//        return sql;
//    }

    public String renderContent(String content, Map<String, Object> params, List<Object> paraList) {
        String key = HashUtils.md5(content);
        if (stringLoader.findTemplateSource(key) == null) {
            stringLoader.putTemplate(key, content);
        }
        return render(key, null, params, paraList);
    }

    public SQLContext getSqlParaPage(String key, String dbType, Map<String, Object> data) {
        Template template = getTemplate(key, dbType);
        return getSqlParaPageTemplate(template, data);
    }

    public SQLContext getSqlParaPageSource(String source, Map<String, Object> data) {
        try {
            String key = HashUtils.md5(source);
            if (stringLoader.findTemplateSource(key) == null) {
                stringLoader.putTemplate(key, source);
            }
            Template template = cfg.getTemplate(key);
            return getSqlParaPageTemplate(template, data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected SQLContext getSqlParaPageTemplate(Template template, Map<String, Object> data) {
        if (template == null) {
            return null;
        }
        SQLContext context = new SQLContext();
        try {
            List<Object> paraList = new ArrayList<>();
            StringWriter out = new StringWriter();
            data.put(TemplateConstants.SQL_PARA_KEY, paraList);
            template.process(data, out);
            String listSql = out.toString();
//        listSql = SqlHelp.compressSql(listSql);
            context.setListSql(listSql);
            if (!paraList.isEmpty()) {
                List<SQLParameter> list = new ArrayList<>();
                for (Object o : paraList) {
                    SQLParameter p = new SQLParameter();
                    p.setValue(o);
                    list.add(p);
                }
                context.setParas(list);
            }
            data.remove(TemplateConstants.SQL_PARA_KEY);    // 避免污染传入的 Map

            out = new StringWriter();
            data.put(TemplateConstants.SQL_PARA_KEY, new IgnoreEmptyList());
            data.put(TemplateConstants.SQL_COUNT_KEY, true);
            template.process(data, out);
            String countSql = out.toString();
            context.setCountSql(countSql);
            data.remove(TemplateConstants.SQL_PARA_KEY);// 避免污染传入的 Map
            data.remove(TemplateConstants.SQL_COUNT_KEY);// 避免污染传入的 Map
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return context;
    }

    public static Object getRootDataModel(Environment env) {
        TemplateHashModel rootDataModel = env.getDataModel();
        return getUnwrap(rootDataModel);
    }

    private static BeansWrapper bw;

    public static Object getUnwrap(TemplateModel model) {
        if (bw == null) {
            BeansWrapperBuilder builder = new BeansWrapperBuilder(Configuration.VERSION_2_3_30);
            bw = builder.build();
        }
        try {
            return bw.unwrap(model);
        } catch (TemplateModelException e) {
            throw new RuntimeException(e);
        }
    }
}
